HDDPORT equ 0x1f0       ;磁盘端口地址
section code align=16 vstart=0x7c00

    ;将起始扇区号存到寄存器中，由于是 32 位，所以使用两个 16 位的寄存器来保存
    mov si, [READSTART]         ;保存起始扇区号的低 16 位
    mov cx, [READSTART+0x02]    ;保存起始扇区号的高 16 位
    mov al, [SECTORNUM]         ;保存要读取的扇区数量
    push ax                     ;由于后面的指令要用到 ax 寄存器，所以这里需要先暂存起来

    ;由于目标内存地址是物理地址，所以我们需要先将其转换为逻辑地址
    ;要进行 32 位的除法，根据 div 指令的规范，需要将被除数的高 16 位存放到 dx 中，低 16 位存放到 ax 中
    mov ax, [DESTMEM]           ;存放被除数的低 16 位
    mov dx, [DESTMEM+0x02]      ;存放被除数的高 16 位
    mov bx, 16                  ;由于段地址是需要 16 字节对齐的，所以需要除以 16
    div bx                      ;商会保存到 ax 中，就可以作为目标内存地址的段地址了

    mov ds, ax                  ;将目标内存的段地址存放到 ds 中
    xor di, di                  ;将 di 置零
    pop ax                      ;恢复 ax 寄存器

    call ReadHDD

    ResetSegment:
    mov bx, 0x04                ;保存程序第一个段地址，在 0x04 内存处
    mov cl, [0x10]              ;段的总个数，在偏移位 0x10 内存处

    .reset:                     ;重定位段地址
    mov ax, [bx]                ;保存第一个段地址的低 16 位              
    mov dx, [bx+2]              ;保存第一个段地址的高 16 位
    add ax, [cs:DESTMEM]        ;第一个段地址的物理地址的低 16 位，汇编地址加上实际的物理地址
    adc dx, [cs:DESTMEM+2]      ;第一个段地址的物理地址的高 16 位，汇编地址加上睡觉的物理地址
    
    mov si, 16
    div si                      ;将物理地址右移 16 位就得到物理地址对应的段地址
    mov [bx], ax                ;将计算得到的正确地址再写回去
    add bx, 4                   ;偏移地址后移，指向下一个段
    loop .reset                 ;循环直到所有的段都已经重新定位
    
    ResetEntry:                 ;重定位程序入口
    mov ax, [0x13]              ;保存程序入口的低 16 位
    mov dx, [0x15]              ;保存程序入口的高 16 位
    add ax, [cs:DESTMEM]        ;程序入口的物理地址低 16 位，汇编地址加上实际的物理地址
    adc dx, [cs:DESTMEM+2]      ;程序入口的物理地址高 16 位，汇编地址加上实际的物理地址

    mov si, 16
    div si                      ;将物理地址右移 16 位就得到物理地址对应的段地址
    mov [0x13], ax              ;将计算得到的正确地址再写回去

    jmp far [0x11]              ;跳转到程序入口

;声明读取硬盘的函数。输入参数有三个，起始扇区号、要读取的扇区数量、目标地址，这三个参数已经设置到了对应的内存地址中
;起始扇区号：cx 高 16 位，si 低 16 位。
;要读取的扇区数量：al。
;目标地址：ds 目标地址的段地址，di 目标地址的偏移地址
ReadHDD:
    .setup:                 ;在读取前需要设置磁盘的寄存器
    push ax                 ;在真正执行指令之前，需要将原来寄存器中的状态压入栈中
    push bx
    push cx
    push dx
    ;第一步：告诉硬盘要读取几个扇区
    mov dx, HDDPORT+2       ;存储要读取的扇区数将要写入的目标端口地址
    out dx, al              ;将要读取的扇区数写入目标端口地址
    ;第二步：告诉硬盘从哪个逻辑扇区开始读
    mov dx, HDDPORT+3       ;0x1f3 保存的是 0~7 位
    mov ax, si              ;将起始扇区号的低 16 位保存到 ax 中
    out dx, al              ;由于 0x1f3 是一个 8 位的端口，所以传入 al

    mov dx, HDDPORT+4       ;0x1f4 保存的是 8~15 位
    mov al, ah
    out dx, al              ;由于 0x1f4 是一个 8 位的端口，所以传入 ah

    mov dx, HDDPORT+5       ;0x1f5 保存的是 16~23 位
    mov ax, cx              ;将起始扇区号的高 16 位保存到 ax 中
    out dx, al              ;由于 0x1f5 是一个 8 位的端口，所以传入 al
    ;第三步：选择硬盘号和选择读写模式同时还要写入起始扇区号的
    mov dx, HDDPORT+6       ;0x1f6 的低 4 位保存的是起始扇区号 24~27 位，高 4 位保存的是硬盘号和读写模式
    ;mov al, ah
    and al, 0x0f            ;将 al 的高 4 位置零
    mov ah, 0xe0            ;采用 LBA 模式，并且从主硬盘读取，所以是 1110
    or al, ah               ;将起始扇区号的 24～27 位和设备属性进行合并
    out dx, al              ;由于 0x1f6 是一个 8 位的端口，所以传入 al
    ;第四步：告诉硬盘需要执行读取操作
    mov dx, HDDPORT+7       ;0x1f7 保存的是命令
    mov al, 0x20            ;0x20 表示读取扇区
    out dx, al              ;由于 0x1f7 是一个 8 位的端口，所以传入 al
    
    ;第五步：查看硬盘状态，是否准备就绪
    .waits:
    in al, dx               ;0x1f7 也保存了磁盘的状态
    and al, 0x88            ;把除 3、7 位的其他位都置零
    cmp al, 0x08            ;判断磁盘空闲（第 7 位为 0），且已经就绪了（第 3 位为 1）
    jnz .waits              ;如果比较结果不为 0，则继续等待
    
    ;第六步：循环读取硬盘
    mov dx, HDDPORT         ;0x1f0 端口保存了待读取的数据，这是一个 16 位的端口，一次可以读取一个字节
    mov cx, 256             ;一个扇区是 512 字节，所以需要读取 256 次

    .readword:
    in ax, dx               ;将端口的数据保存到 ax 寄存器中
    mov [ds:di], ax         ;将数据保存到目标内存地址中
    add di, 2               ;将目标内存地址偏移量+2，因为一次传输的是一个字的数据
    ;or ah, 0x00            ;判断是否已经读取到了结尾
    ;jnz .readword          ;如果没有读到结尾则继续读下一个字
    loop .readword

    .return:
    pop dx                  ;在执行完指令，返回之前将寄存器中的状态恢复到执行函数之前
    pop cx
    pop bx
    pop ax
    ret

READSTART dd 1          ;读取硬盘的起始扇区号
SECTORNUM dd 1          ;要读取扇区的数量
DESTMEM   dd 0x10000    ;目标内存地址

End: jmp End
times 510-($-$$) db 0
                 db 0x55, 0xaa